﻿//Source: http://wpfwin32renderer.codeplex.com/
using System;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;

namespace ScrumTable.UI.View.WinTouch.Win32Interopt
{
    public class InteropBitmapRenderer : RasterRendererElement
    {
        #region P/Invoke
        [SuppressUnmanagedCodeSecurity]
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern IntPtr CreateFileMapping(IntPtr hFile,
                                               IntPtr lpFileMappingAttributes,
                                               uint flProtect,
                                               uint dwMaximumSizeHigh,
                                               uint dwMaximumSizeLow,
                                               string lpName);

        [SuppressUnmanagedCodeSecurity]
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern IntPtr MapViewOfFile(IntPtr hFileMappingObject,
                                           uint dwDesiredAccess,
                                           uint dwFileOffsetHigh,
                                           uint dwFileOffsetLow,
                                           uint dwNumberOfBytesToMap);

        [SuppressUnmanagedCodeSecurity]
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool UnmapViewOfFile(IntPtr lpBaseAddress);

        [SuppressUnmanagedCodeSecurity]
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool CloseHandle(IntPtr hHandle);

        private const uint FILE_MAP_ALL_ACCESS = 0xF001F;

        private const uint PAGE_READWRITE = 0x04;

        private readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
        #endregion

        /// <summary>
        /// Our interop bitmap we use to render to WPF
        /// </summary>
        private InteropBitmap m_renderedBitmap;

        /// <summary>
        /// Cached size of the last image we rendered
        /// </summary>
        private Size m_lastImageSize;

        /// <summary>
        /// Native pixel buffer that contains the pixels for WPF to render
        /// </summary>
        private IntPtr m_pBackBuffer;

        /// <summary>
        /// Our mapped file
        /// </summary>
        private IntPtr m_fileMappingSection;

        /// <summary>
        /// How many bits per pixel are in each bitmap
        /// </summary>
        private const int BITS_PER_PIXEL = 4;

        /// <summary>
        /// Ensures we have a WPF interopbitmap to write our pixels to
        /// </summary>
        /// <param name="width">Width of the bitmap</param>
        /// <param name="height">Height of the bitmap</param>
        [SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
        private void InitializeRenderBitmap(int width, int height)
        {
            /* Release all resources we may have been using before*/
            ReleaseResources();

            uint cPixels = (uint)(width * height);
            uint cBytes = cPixels * BITS_PER_PIXEL;

            /* Create a file mapping via P/Invoke */
            m_fileMappingSection = CreateFileMapping(INVALID_HANDLE_VALUE,
                                                     IntPtr.Zero,
                                                     PAGE_READWRITE,
                                                     0,
                                                     cBytes,
                                                     null);

            /* Creates our system memory pixel buffer */
            m_pBackBuffer = MapViewOfFile(m_fileMappingSection,
                                          FILE_MAP_ALL_ACCESS,
                                          0,
                                          0,
                                          cBytes);

            /* We only deal with ARGB */
            var format = PixelFormats.Bgra32;

            int stride = (width * format.BitsPerPixel + 7) / 8;

            /* Creates a new interop bitmap from our map view */
            m_renderedBitmap = (InteropBitmap)Imaging.CreateBitmapSourceFromMemorySection(m_fileMappingSection,
                                                                                          width,
                                                                                          height,
                                                                                          format,
                                                                                          stride,
                                                                                          0);
            /* Set our WPF image source to our interop bitmap */
            RenderedImage.Source = m_renderedBitmap;

            m_lastImageSize = new Size(width, height);
        }

        /// <summary>
        /// Releases all unmanaged resources that have been allocated
        /// </summary>
        private void ReleaseResources()
        {
            if (m_pBackBuffer != IntPtr.Zero)
            {
                UnmapViewOfFile(m_pBackBuffer);
                m_pBackBuffer = IntPtr.Zero;
            }

            if (m_fileMappingSection != IntPtr.Zero)
            {
                CloseHandle(m_fileMappingSection);
                m_fileMappingSection = IntPtr.Zero;
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
                ReleaseResources();

            base.Dispose(disposing);
        }

        public override void RenderFrame(RenderedFrameEventArgs frameArgs)
        {
            int width = frameArgs.Width;
            int height = frameArgs.Height;

            /* Ensure a WPF bitmap to render to */
            if (m_renderedBitmap == null)
            {
                /* If we are on the dispatcher thread, don't bother dispatching */
                if (Dispatcher.CheckAccess())
                    InitializeRenderBitmap(width, height);
                else
                {
                    Dispatcher.Invoke((Action)(() => InitializeRenderBitmap(width, height)));
                    return;
                }
            }

            /* Ensure we have the right size image */
            if (m_lastImageSize.Height != height || m_lastImageSize.Width != width)
            {
                /* If we are on the dispatcher thread, don't bother dispatching */
                if (Dispatcher.CheckAccess())
                    InitializeRenderBitmap(width, height);
                else
                {
                    Dispatcher.Invoke((Action)(() => InitializeRenderBitmap(width, height)));
                    return;
                }
            }

            /* Copy our buffer to the WPF bitmap buffer */
            CopyMemory(m_pBackBuffer, frameArgs.Buffer, frameArgs.BufferSize);

            if (m_renderedBitmap == null)
                return;

            /* Invalidate() tells WPF to upload that ish to the GPU */
            if (Dispatcher.CheckAccess())
                m_renderedBitmap.Invalidate();
            else
                Dispatcher.Invoke((Action)(() => m_renderedBitmap.Invalidate()));
        }
    }
}
